package com.poseidon.prototype;


import PackagePoseidon.DirectionData;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.ChaseCamera;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme.input.InputHandler;
import com.jme.input.KeyInput;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.intersection.BoundingPickResults;
import com.jme.intersection.PickResults;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Plane;
import com.jme.math.Quaternion;
import com.jme.math.Ray;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.CameraNode;
import com.jme.scene.Skybox;
import com.jme.scene.shape.Box;
import com.jme.scene.state.CullState;
import com.jme.scene.state.FogState;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;
import com.jmex.effects.water.ProjectedGrid;
import com.jmex.effects.water.WaterHeightGenerator;
import com.jmex.effects.water.WaterRenderPass;
import com.jmex.physics.StaticPhysicsNode;
import com.jmex.physics.material.Material;
import com.jmex.physics.util.SimplePhysicsGame;
import com.jmex.terrain.TerrainPage;
import com.jmex.terrain.util.FaultFractalHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;
import java.util.Observable;
import java.util.Observer;
import javax.swing.ImageIcon;
import jmetest.effects.water.TestProjectedWater;
import jmetest.terrain.TestTerrain;

public class Simulator  extends SimplePhysicsGame implements Observer   {


 private Robot robot;
 private StaticPhysicsNode floor;
 private Physics physics;
 private CameraNode cn;
 OPSInterface opsInt;

 //Enviroment presentation
 private WaterRenderPass waterEffectRenderPass;
 private ProjectedGrid projectedGrid;


 private void onNewDataJoystik(DirectionData dataDir) {

        //System.out.print("The simulator reads a new direction: ");
        switch (Integer.parseInt(dataDir.direction)) {
            case 0:// System.out.println("FORWARD");
                    robot.enableEngine( 0 );
                    break;
            case 1: //System.out.println("UP");
                    robot.enableEngine( 1 );
                    break;
            case 2: //System.out.println("DOWN");
                    robot.enableEngine( 2 );
                    break;
            case 3: //System.out.println("LEFT");
                    robot.enableEngine( 3 );
                    break;
            case 4: //System.out.println("RIGHT");
                    robot.enableEngine( 4 );
                    break;
        }
    }

 public void update(Observable obs,Object arg){
        this.onNewDataJoystik((DirectionData)arg);;
 }


 @Override
 protected void simpleInitGame() {
 
  //create the floor
  createFloor();

  //initialize the lights
  initLights();

  //build he lights
  buildLight();

  //creates the fog
  createFog();

  //gets the physics from the COLLADA file
  parsePhysics();


  //Create the robot
  createRobot();

  //Set the camera node for the robot
  setFirstPersonCameraNode();
  setThirdPersonCameraNode();


  //set gravity to zero
  getPhysicsSpace().setDirectionalGravity(new Vector3f(0,0,0));

  //initialize the input
  initInput();

  //create the elements to make OPS work
  initOPS(physics);
 

  //update the Render state
  rootNode.updateRenderState();
 }

 /**
	 * Samuele Sabbatini
     *
     * This method is used to create a continued forces. This method is called
     * every step and it calculate the power of the physics force every step.
     *
     * @param
     * @return acceleration
     */
	@Override
	protected void simpleUpdate(){
	 robot.physicsForces();
	};


 private void createOBJ(){
    Box b1 = new Box("box 1", new Vector3f(-5, -5, -5), new Vector3f(5, 5, 5));
    Box b2 = new Box("box b2", new Vector3f(-5, -5, -5), new Vector3f(5, 5, 5));
    Box b3 = new Box("box b3", new Vector3f(-5, -5, -5), new Vector3f(5, 5, 5));

    b1.setModelBound(new BoundingBox());
    b2.setModelBound(new BoundingBox());
    b3.setModelBound(new BoundingBox());

    b1.setLocalTranslation(new Vector3f(0, 10, 30));
    b2.setLocalTranslation(new Vector3f(0, 0, 10));
    b3.setLocalTranslation(new Vector3f(10, 0, 30));

    rootNode.attachChild(b1);
    rootNode.attachChild(b2);
    rootNode.attachChild(b3);

 }

 private void parsePhysics(){
		XMLparse xml= new XMLparse("C:/Documents and Settings/Francesco/Desktop/Poseidon2/Poseidon2/src/jmetest/data/model/falconTest.dae");
		physics= xml.getPhysics();
		//System.out.println(physics.toString());

	};

 private void  setFirstPersonCameraNode(){
      cn = new CameraNode("camera node",cam);
      Quaternion pitch90 = new Quaternion();
      pitch90.fromAngleAxis(FastMath.PI/2,new Vector3f(1,0,0));

      cn.setLocalRotation(pitch90);
      cn.getLocalTranslation().z +=10;
      cn.getLocalTranslation().y -=10;
 }


private void   setThirdPersonCameraNode(){
 input.removeFromAttachedHandlers(cameraInputHandler);
 cameraInputHandler=new ChaseCamera(cam,robot.getChassis().getChild(0));
 input.addToAttachedHandlers(cameraInputHandler);


}
 private void initOPS(Physics physics){
      opsInt = new OPSInterface(physics);
      opsInt.addDirectionObserverOPS(this);
      opsInt.sendPhysicsInfo();
 }

 /**
	 * Samuele Sabbatini
     *
     *  Just instantiates our Robot and attaches it to the scene graph.
     *
     *
     * @param
     * @return
     */
	private void createRobot(){
		robot = new Robot(  getPhysicsSpace(),this.physics);
		robot.setLocalTranslation(0,10,0);
		rootNode.attachChild(robot);
	}


  

 


 public static void main(String[] args) throws InterruptedException {
        Logger.getLogger( "" ).setLevel( Level.WARNING );

        Simulator simulator = new Simulator();
        simulator.setConfigShowMode(ConfigShowMode.AlwaysShow);

        

        simulator.start();
 }

 private void initLights( ) {
        lightState.setEnabled(true);
        lightState.setGlobalAmbient(new ColorRGBA(1,1,1,1));
        lightState.setTwoSidedLighting(true);
        rootNode.setRenderState(lightState);
 }

 private void initInput() {
        //  Simple chase camera
       //  input.removeFromAttachedHandlers( cameraInputHandler );
       //  cameraInputHandler = new ChaseCamera( cam, robot.getChassis().getChild( 0 ) );
       //  input.addToAttachedHandlers( cameraInputHandler );

       //  Attaching the custom input actions (and its respective keys) to the carInput handler.

     /*
       //  Move Forward
         input.addAction( new AccelAction( robot, 0 ),
                 InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_SPACE, InputHandler.AXIS_NONE, false );
         //Move Up
         input.addAction( new AccelAction( robot, 1 ),
                 InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_Y, InputHandler.AXIS_NONE, false );
         //Move Down
         input.addAction( new AccelAction( robot, 2 ),
                 InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_H, InputHandler.AXIS_NONE, false );
         //Move Left
         input.addAction( new AccelAction( robot, 3 ),
                 InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_G, InputHandler.AXIS_NONE, false );
         //Move right
         input.addAction( new AccelAction( robot, 4 ),
                 InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_J, InputHandler.AXIS_NONE, false );
        */
        input.addAction( new Set3personCamera(),
                 InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_1, InputHandler.AXIS_NONE, false );
      
      
        input.addAction( new ResetPoseidon(),
                 InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_R, InputHandler.AXIS_NONE, false );

        input.addAction( new SendOpsInfo(),
                 InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_SPACE, InputHandler.AXIS_NONE, false );

        input.addAction( new tryRay(),
                 InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_I, InputHandler.AXIS_NONE, false );
  }



private boolean camera3person = false;
private class tryRay extends InputAction {
    public void performAction(InputActionEvent evt){

        PickResults pr;

        pr = new BoundingPickResults(); //container for our "pick results"

        // Create a ray starting from the camera, and going in the direction

        // of the mouse's location

        //if we the camera in in third person

        Ray robotRay = new Ray(cam.getLocation(),cam.getDirection());

        //Or more easy:

        //1. position in the robot camera LOCATION

        //2. DIRECTION

        // Ray mouseRay = new Ray(worldCoords, worldCoords2.subtractLocal(worldCoords).normalizeLocal());

        // Does the mouse's ray intersect the box's world bounds?

        pr.clear();//clear any previous pick results I may have had


        //, I have to initiate the “pick” call. I am going to see if any children of rootNode intersect with the ray I just created, and if so their results are stored in pr. For each item that intersected that ray, I’m changing its color.

        rootNode.findPick(robotRay, pr);

        for (int i = 0; i < pr.getNumber(); i++) {
            
        //    pr.getPickData(i).getTargetMesh().setRandomColors();

            System.out.println("got it!!!");

            System.out.println(pr.getPickData(i).getTargetMesh().getName());

            // System.out.println(poseidon1.getEngine().getLocalTranslation());

            // System.out.println(pr.getPickData(i).getTargetMesh().getLocalTranslation());

            //ARE THE SAME INSTRUCTION BUT THE FIRST IS BETTEEEEEEER

     //       System.out.println(poseidon1.getEngine().getLocalTranslation().subtract(pr.getPickData(i).getTargetMesh().getLocalTranslation()));

       //     System.out.println(cam.getLocation().subtract(cam.getDirection()));


      }

}
}

private class Set3personCamera extends InputAction {
    public void performAction(InputActionEvent evt){
       
        robot.getChassis().attachChild(cn);


    }
 }

private class ResetPoseidon extends InputAction {
    public void performAction(InputActionEvent evt){
        robot.getChassis().setLocalTranslation(new Vector3f(0, 5f, 0));
        //robot.clearRobotDynamic();


        robot.getChassis().detachChild(cn);

        camera3person = false;
        cameraPerspective();
        Vector3f loc = new Vector3f(0.0f,0.0f,25.0f);
        Vector3f left = new Vector3f(-1.0f,0.0f,0.0f);
        Vector3f up = new Vector3f(0.0f,1.0f,0.0f);
        Vector3f dir = new Vector3f(0.0f,0.0f,-1.0f);

        cam.setFrame(loc, left,up,dir);
        cam.update();
        display.getRenderer().setCamera(cam);

    }
}



private class SendOpsInfo extends InputAction {
    public void performAction(InputActionEvent evt){

        //Speed
        String[] speObj = new String[3];
        speObj[0] = Float.toString(robot.getChassis().getLinearVelocity(null).x);
        speObj[1] = Float.toString(robot.getChassis().getLinearVelocity(null).y);
        speObj[2] = Float.toString(robot.getChassis().getLinearVelocity(null).z);

        
        //Angulation
        String[] angObj = new String[3];
        angObj[0] = Float.toString(robot.getChassis().getLocalRotation().x);
        angObj[1] = Float.toString(robot.getChassis().getLocalRotation().y);
        angObj[2] = Float.toString(robot.getChassis().getLocalRotation().z);


        //Distance
        String[] disObj = new String[3];
        disObj[0] = Float.toString(robot.getChassis().getLocalTranslation().x);
        disObj[1] = Float.toString(robot.getChassis().getLocalTranslation().y);
        disObj[2] = Float.toString(robot.getChassis().getLocalTranslation().z);



        opsInt.writeInfoOPS("Speed", speObj);
        opsInt.writeInfoOPS("Angulation", angObj);
        opsInt.writeInfoOPS("Distance", disObj);

        System.out.println("Linear Velocity (Speed): " + speObj.toString());
        System.out.println("Local Rotation (Angulation): " + angObj.toString());
        System.out.println("Translation (Distance): " + disObj.toString());
        System.out.println("");
        

        //robot.getChassis().getLocalTranslation().x;
        //robot.getChassis().getLocalTranslation().y;
        //robot.getChassis().getLocalTranslation().z;

       //only the Y change System.out.println("Velocita angolare "+robot.getChassis().getAngularVelocity(null));

        
    }
 }

    private void buildLight() {
		//Light
		DirectionalLight dl = new DirectionalLight();
	    dl.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
	    dl.setDirection(new Vector3f(1, -0.5f, 1));
	    dl.setEnabled(true);
	    lightState.attach(dl);

	    CullState cs = display.getRenderer().createCullState();
	    cs.setCullFace(CullState.Face.Back);
	    cs.setEnabled(true);
	    rootNode.setRenderState(cs);
	}

	private void createFog() {
		FogState fog = display.getRenderer().createFogState();
	    fog.setDensity(5.0f);
	    fog.setEnabled(true);
	    fog.setColor(new ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f));
	    fog.setEnd(350);
	    fog.setStart(0);
	    fog.setDensityFunction(FogState.DensityFunction.Linear);
	    fog.setQuality(FogState.Quality.PerVertex);
	    rootNode.setRenderState(fog);
	}

    private void createFloor() {
		// Add terrain
		FaultFractalHeightMap heightMap = new FaultFractalHeightMap(257, 32, 0, 255, 0.75f);
		heightMap.setHeightScale(0.1f);

		Vector3f terrainScale = new Vector3f(20, 0.3f, 20);

		TerrainPage terrain = new TerrainPage("Terrain", 33, heightMap.getSize(), terrainScale, heightMap.getHeightMap());

		terrain.setDetailTexture(1, 16);
		terrain.setLocalTranslation(new Vector3f(0.0f, -80.0f, 0.0f));

		// Texture of the terrain
		ProceduralTextureGenerator pt = new ProceduralTextureGenerator(heightMap);
	    pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader().getResource(
	        "jmetest/data/texture/terrain/baserock.jpg")), -128, 0, 128);
	    pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader().getResource(
	        "jmetest/data/texture/dirt.jpg")), 0, 128, 384);

	    pt.createTexture(512);

	    TextureState ts = display.getRenderer().createTextureState();
	    ts.setEnabled(true);

	    Texture t1 = TextureManager.loadTexture(
	        pt.getImageIcon().getImage(),
	        Texture.MinificationFilter.Trilinear,
	        Texture.MagnificationFilter.Bilinear,
	        true);
	    ts.setTexture(t1, 0);

	    Texture t2 = TextureManager.loadTexture(
	    	TestTerrain.class.getClassLoader().getResource(
	        "jmetest/data/texture/Detail.jpg"),
	        Texture.MinificationFilter.Trilinear,
	        Texture.MagnificationFilter.Bilinear);
	    ts.setTexture(t2, 1);

	    t2.setWrap(Texture.WrapMode.Repeat);

	    t1.setApply(Texture.ApplyMode.Combine);
	    t1.setCombineFuncRGB(Texture.CombinerFunctionRGB.Modulate);
	    t1.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);
	    t1.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);
	    t1.setCombineSrc1RGB(Texture.CombinerSource.PrimaryColor);
	    t1.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);

	    t2.setApply(Texture.ApplyMode.Combine);
	    t2.setCombineFuncRGB(Texture.CombinerFunctionRGB.AddSigned);
	    t2.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);
	    t2.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);
	    t2.setCombineSrc1RGB(Texture.CombinerSource.Previous);
	    t2.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);
	    rootNode.setRenderState(ts);

	    // Physics of the terrain
		floor = getPhysicsSpace().createStaticNode();
		floor.attachChild( terrain );
		floor.generatePhysicsGeometry();
		floor.setMaterial( Material.CONCRETE );
        floor.setLocalTranslation( new Vector3f( 0, -0.1f, 0 ) );

        rootNode.attachChild(floor);
    }


}